"""
Copyright 2012 Wieser Dietmar, Margit Mutschlechner.

This file is part of Multimedia Social Networking Platform.

Multimedia Social Networking Platform is free software: 
you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

Multimedia Social Networking Platform is distributed
in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with Multimedia Social Networking Platform.  
If not, see <http://www.gnu.org/licenses/>.

"""

from threading import Thread
from msnp.logger import Logger
from msnp.service.network.model import Model, CustodyModel
from msnp.persistence import PersistenceController
#from msnp.service.network.model.CustodyModel import SendMessage
from msnp.service import synch
from msnp.service.utils import Hash
from msnp.model import PTPModel
from msnp.gui import MySignal
from msnp.service import utils
from msnp.service import network
from msnp.service.network import sending
from msnp.service.network import logic
from msnp.model.PTPModel import BackupContent
  

def sendMediaContentSinceLastSynch(senderIdentity, senderName, group, timestamp):
    """This function sends the corresponding media content to the identity.
    
    Based on the group and name the current peer is able to retrieve changes from the own database.
    
    Args:
        senderIdentity: the PeerInfo instance of the requesting peer.
        senderName: the name of the sender identity.
        group: a Group instance.
        timestamp: the timestamp
    """
    
    content = synch.NetSynchController.getMediaContentToSendSinceLastSynch(senderName, group, timestamp)
    if content != None:
        timestamp = utils.DateFormatter.formatTimeAsFloat()
        sending.Sender.sendMediaContent([content], timestamp, senderIdentity, senderName)
    else:
        Logger.info("nothing found to send")


##########################
## execute remote calls ##
##########################

def forwardRingReport(ringReportEntries, fingerValidationDict, backupValidationDict, alreadyReceivedUsers, type):
    """This function executes the ring report message.
    
    Args:
        ringReportEntries: all collected RingReportEntry instances
        fingerValidationDict: a dictinary for validating the fingertables
        backupValidationDict: a dictinary for validating the backups
        alreadyReceivedUsers: a list of peers that already received the message
        type: the report type (defined in RingReportType)
    """
    
    myName = network.NetworkController.getMySelf().getMyIdentity().getName()
    Logger.info("--------> processing ring report of type " + type)
    #extend the ring report with own entries and forward it if I am not the original sender
    if alreadyReceivedUsers.__contains__(myName) == False:
        if type == Model.RingReportType.VALIDATE_FINGERS_AND_BACKUP:
            #only a print for testing
            backups = network.BackupController.selectWholeBackup()
            Logger.info("DATABASE CONTENT:")
            for backup in backups:
                Logger.info(backup.toString())
            peerValidationDict = network.NetworkValidator.checkFingertable(ringReportEntries, fingerValidationDict)
            backupValidationDict = network.NetworkValidator.checkBackup(ringReportEntries, backupValidationDict)
        else:
            ringReportEntries.append(network.NetworkValidator.generateRingReportEntry())
        alreadyReceivedUsers.append(myName)
        sending.Sender.forwardRingReport(ringReportEntries, fingerValidationDict, backupValidationDict, alreadyReceivedUsers, type)
    #if the ring report got forwarded through the whole ring and reached the original sender again
    else:
        alreadyReceivedUsers = [myName]
        #original sender wants just to display the ring
        if type == Model.RingReportType.PRINT_RING:
            network.NetworkValidator.printRingReportEntries(ringReportEntries)
        #original sender wants a validation of the ring and the fingertables
        #-> first round: type=VALIDATE_RING, peers extends the ring report with their name, change type when arriving at original sender
        #-> second round: type=VALIDATE_ALL_FINGERS, peers extends the fingerValidationDict with the wrong entries of their fingertables, print evaluation when arriving at original sender
        elif type == Model.RingReportType.VALIDATE_RING:
            sending.Sender.forwardRingReport(ringReportEntries, fingerValidationDict, backupValidationDict, alreadyReceivedUsers, Model.RingReportType.VALIDATE_FINGERS_AND_BACKUP)
        elif type == Model.RingReportType.VALIDATE_FINGERS_AND_BACKUP:
            #only a print for testing
            backups = network.BackupController.selectWholeBackup()
            Logger.info("DATABASE CONTENT:")
            for backup in backups:
                Logger.info(backup.toString())
            fingerValidationDict = network.NetworkValidator.checkFingertable(ringReportEntries, fingerValidationDict)
            backupValidationDict = network.NetworkValidator.checkBackup(ringReportEntries, backupValidationDict)
            network.NetworkValidator.printFingerValidationEntry(fingerValidationDict)
            network.NetworkValidator.printRingValidation(ringReportEntries)
            network.NetworkValidator.printBackupValidation(backupValidationDict)
            #network.NetworkValidator.printAllPeersBackuped(ringReportEntries)
            #network.NetworkValidator.printLowLevelValidation(ringReportEntries)

def sendMediaContent(content, timestamp, receiverName):
    """This function executes the transmission of media contents.
    
    If the current peer is the receiver it calls the receiveMediaContent function in the NetSynchController.
    Otherwise if its one of the current peers online low level peers it stores a message in the custody
    queue, if not it does nothing.
    
    Args:
        content: the transmitted media content.
        timestamp: the timestamp
        receiverName: the name of the receiver (string)
    """
    
    myName = network.NetworkController.getMySelf().getMyIdentity().getName()
    if receiverName != myName:
        lowLevelPeer = network.NetworkController.getMySelf().getLowLevelPeerByName(receiverName)
        if lowLevelPeer != None:
            Logger.info("Media content received for my low level peer " + receiverName)
            network.CustodyQueue.addMediaContentMessage(receiverName, content, timestamp)
        else:
            hashedNameLong = long(Hash.hash(receiverName), 16)
            if logic.ResponsibilityLogic.amIResponsibleIdentityFor(hashedNameLong):
                Logger.info("Media content received for a peer that is now offline: " + receiverName)
            else:
                Logger.info("Media content received for a peer that is no more my low level peer: " + receiverName)
                nearestIdentity = network.NetworkController.getMySelf().getNearestIdentity(hashedNameLong)[0]
                sending.Sender.sendMediaContent(content, timestamp, nearestIdentity, receiverName)
    else:
        network.NetworkLock.acquireMediaLock()
        try:
            synch.NetSynchController.receiveMediaContent(content, timestamp)
        finally:
            network.NetworkLock.releaseMediaLock()

def pollMediaContentSinceLastSynch(senderIdentity, senderName, receiverName, group, timestamp):
    """This function tries to poll all media content that may changed since a certain timestamp.
    
    This call is routed through the ring until it reaches the responsible peer of the reciever name.
    If this peer is a super peer it sends the corresponding media content,
    otherwise if its one of the online low level peers it leaves a message in the custody queue.
    
    Args:
        senderIdentity: the sender identity (PeerInfo instance)
        senderName: the name of the sender (string)
        receiverName: the name of the receiver (string)
        group: the Group instance
        timestamp: the timestamp
    """
    
    if logic.ResponsibilityLogic.amIResponsibleIdentityFor(receiverName):
        myName = network.NetworkController.getMySelf().getMyIdentity().getName()
        if receiverName == myName:
            sendMediaContentSinceLastSynch(senderIdentity, senderName, group, timestamp)
        else:
            llPeer = network.NetworkController.getMySelf().getLowLevelPeerByName(receiverName)
            if llPeer != None:
                Logger.info("Media content since last synch request for my low level peer, put in queue")
                network.CustodyQueue.addGetMediaContentSinceLastSynchMessage(receiverName, senderIdentity, senderName, group, timestamp)
            else:
                Logger.info("Media content since last synch request for a peer that is now offline")
    else:
        sending.Sender.pollMediaContentSinceLastSynch(senderIdentity, senderName, receiverName, group, timestamp)


def forwardPersonalMessage(receiverName, senderName, senderRespIdentity, message, timestamp):
    """This function forwards a personal message to the responsible peer of receiver name.
    
    Args:
        receiverName: the name of the receiver (string)
        senderName: the name of the sender (string)
        senderRespIdentity: the responsible identity of the sender (PeerInfo instance)
        message: the personal message (string)
        timestamp: the timestamp
    """
    
    Logger.info("received personal message from " + senderName)
    logic.ResponsibilityLogic.forwardPersonalMessage(receiverName, senderName, senderRespIdentity, message, timestamp)


def callbackPersonalMessage(receiverRespIdentity, receiverName, senderName, timestamp, status):
    """This function is called after the reception of a personal message.
    
    Args:
        receiverRespIdentity: the responsible identity of the receiver (PeerInfo instance)
        receiverName: the name of the receiver (string)
        senderName: the name of the sender (string)
        timestamp: the timestamp
        status: the status 
    """
    
    Logger.info("received personal message acknowledge from " + senderName)
    myName = network.NetworkController.getMySelf().getMyIdentity().getName()
    #if this peer is the receiver
    if receiverName == myName:
        MySignal.emitChatMessageStatusChangedSignal(timestamp, status, senderName)
    #if this peer is responsible for the receiver (online or offline)
    elif PersistenceController.selectUserImResponsibleForUsername(receiverName) != None:
        network.CustodyQueue.addPersonalMessageCallback(receiverName, senderName, timestamp, status)
    #this peer is no more responsible for the receiver
    else:
        #try to forward the message to the predecessor. the predecessor should be now responsible for the receiver.
        predecessor = network.NetworkController.getMySelf().getPredecessor()
        if predecessor != None:
            sending.Sender.callbackPersonalMessage(predecessor, receiverName, senderName, timestamp, status)
        else:
            Logger.info("Error! problem because i received a pmessage for a receiver i am not responsible for and i have no predecessor for forwarding.")
    
    
def executeKeepAlive(llPeerName, queueType):
    """This function es executed during the keep-alive request of a low level peer.
    
    If the low level peer is a already authenticated (contained in the low-level-peer-list) all matching custody messages with the given queue type and low level peer name are collected.
    If the low level peer is not in the low level peer list yet all hello- and shift-peer-messages are returned.
    If the low level peer is not in the low level peer list yet and no matching messages are int the custody queue a LOW_LEVEL_PEER_NOT_IN_LIST is returned.
    The whole method is locked so that the low level peer can't be shifted to another super peer at the same.
    
    Args:
        llPeerName: the name of the low level peer (string)
        queueType: the type defined by CustodyQueueType
        
    Returns:
        Returns a tuple containing a return type (defined by ReturnType) and a list of CustodyMessage instances.
    """
    
    #lock so that the information can't be shifted while the low level peer retrieves a message
    network.NetworkLock.acquireKeepAliveLock()
    try:
        #if low level peer is in the list I update his keep alive timestamp and return all stored messages
        llPeer = network.NetworkController.getMySelf().getLowLevelPeerByName(llPeerName)
        if llPeer != None:
            llPeer.updateLastKeepAlive()
            messages = network.CustodyQueue.retrieveAndDelete(llPeerName, queueType)
            # if its a memberstatuschanged message we stored it in the db with processed=False
            # therefore set the message to processed=True in db
            updateFingers = False
            for msg in messages:
                if msg.getQueueType() == CustodyModel.CustodyQueueType.MEMBER_STATUS_CHANGED:
                    wasProcessed = msg.isProcessed()
                    msg.setProcessed()
                    PersistenceController.insertMessages(llPeerName, [msg])
                    # set processed flag as before because low level peer has eventually to process it
                    msg.setProcessed(wasProcessed)
                    updateFingers = True
            if updateFingers:
                myName = network.NetworkController.getMySelf().getMyIdentity().getName()
                user = PersistenceController.selectUserImResponsibleForUsername(llPeerName)
                backupContent = BackupContent(myName, [user], None, None, PTPModel.BackupType.UPDATE)
                network.BackupController.updateBackupAtFingers(backupContent)
                
            return (PTPModel.ReturnType.SUCCESSFUL, messages)
        #if low level peer is not in the list I stored only hello- or shifted-peer-messages or authentication-messages (if auth was not successful)
        messages = network.CustodyQueue.retrieveAndDeleteList(llPeerName, [CustodyModel.CustodyQueueType.HELLO_RESULT_MESSAGE, CustodyModel.CustodyQueueType.AUTH_RESULT_MESSAGE, CustodyModel.CustodyQueueType.SHIFT_PEER_MESSAGE])
        if messages != None:
            return (PTPModel.ReturnType.SUCCESSFUL, messages)
        else:
            return (PTPModel.ReturnType.LOW_LEVEL_PEER_NOT_IN_LIST, None)
    finally:
        network.NetworkLock.releaseKeepAliveLock()
        
        
def executeIsAlive():
    """This function just returns True so that the calling peer knows that the current peer is alive.
    
    Returns:
        True
    """
    
    Logger.info("got isAlive request")
    return True

##############################
## threads for remote calls ##
##############################  

class HelloThread(Thread):
    """Thread to search the responsible peer for authentication.
    
    Calls a function in the NetworkController to find the responsible peer for the PeerInfo instance __identity.

    Attributes:
        __identity: the PeerInfo instance
        __isLogin: A boolean determining whether the executed authentication is a registration or a login.
    """
    
    def __init__(self, identity, isLogin):
        """the init method"""
        Thread.__init__(self)
        self.__identity = identity
        self.__isLogin = isLogin
    
    def run(self):
        """This method forwards the call to the findResponsibleIdentityForAuth function of the ResponsibilityLogic module."""
        Logger.info("got hello from " + self.__identity.getName())
        logic.ResponsibilityLogic.findResponsibleIdentityForAuth(self.__identity.getName(), network.NetworkController.getMySelf().getMyIdentity(), self.__isLogin)

        
class ForwardRingReportThread(Thread):
    """Thread that processes the ring report operation.
    
    Attributes:
        __ringReportEntries: all collected RingReportEntry instances
        __fingerValidationDict: a dictinary for validating the fingertables
        __backupValidationDict: a dictinary for validating the backups
        __alreadyReceivedUsers: a list of peers that already received the message
        __type: the report type (defined in RingReportType)
    """
    
    def __init__(self, ringReportEntries, fingerValidationDict, backupValidationDict, alreadyReceivedUsers, type):
        """the init method"""
        Thread.__init__(self)
        self.__ringReportEntries = ringReportEntries
        self.__fingerValidationDict = fingerValidationDict
        self.__backupValidationDict = backupValidationDict
        self.__alreadyReceivedUsers = alreadyReceivedUsers
        self.__type = type
      
    def run(self):
        """This method forwards the call to the forwardRingReport function."""
        forwardRingReport(self.__ringReportEntries, self.__fingerValidationDict, self.__backupValidationDict, self.__alreadyReceivedUsers, self.__type)
        
        
class SendMediaContentThread(Thread):
    """Thread that executes the reception of media contents.
    
    Attributes:
        __content: the transmitted media content.
        __timestamp: the timestamp
        __receiverName: the name of the receiver (string)
    """
    
    def __init__(self, content, timestamp, receiverName):
        """the init method"""
        Thread.__init__(self)
        self.__content = content
        self.__timestamp = timestamp
        self.__receiverName = receiverName
    
    def run(self):
        """This method forwards the call to the sendMediaContent function."""
        sendMediaContent(self.__content, self.__timestamp, self.__receiverName)


class PollMediaContentSinceLastSynchThread(Thread):
    """Thread that processes media content requests (poll).
    
    Attributes:
        __senderIdentity: the sender identity (PeerInfo instance)
        __senderName: the name of the sender (string)
        __receiverName: the name of the receiver (string)
        __group: the Group instance
        __timestamp: the timestamp
    """
    
    def __init__(self, senderIdentity, senderName, receiverName, group, timestamp):
        """the init method"""
        Thread.__init__(self)
        self.__senderIdentity = senderIdentity
        self.__senderName = senderName
        self.__receiverName = receiverName
        self.__group = group
        self.__timestamp = timestamp
        
    def run(self):
        """This method forwards the call to the pollMediaContentSinceLastSynch function."""
        pollMediaContentSinceLastSynch(self.__senderIdentity, self.__senderName, self.__receiverName, self.__group, self.__timestamp)


class ForwardPersonalMessageThread(Thread):
    """Thread that processes the reception of a personal message.
    
    Attributes:
        __receiverName: the name of the receiver (string)
        __senderName: the name of the sender (string)
        __senderRespIdentity: the responsible identity of the sender (PeerInfo instance)
        __message: the personal message (string)
        __timestamp: the timestamp
    """
    
    def __init__(self, receiverName, senderName, senderRespIdentity, message, timestamp):
        """the init method"""
        Thread.__init__(self)
        self.__receiverName = receiverName
        self.__senderName = senderName
        self.__senderRespIdentity = senderRespIdentity
        self.__message = message
        self.__timestamp = timestamp
    
    def run(self):
        """This method forwards the call to the forwardPersonalMessage function."""
        forwardPersonalMessage(self.__receiverName, self.__senderName, self.__senderRespIdentity, self.__message, self.__timestamp)


class CallbackPersonalMessageThread(Thread):
    """Thread that processes the callback to a sent personal message.
    
    Attributes:
        __receiverRespIdentity: the responsible identity of the receiver (PeerInfo instance)
        __receiverName: the name of the receiver (string)
        __senderName: the name of the sender (string)
        __timestamp: the timestamp
        __status: the status 
    """
    
    def __init__(self, receiverRespIdentity, receiverName, senderName, timestamp, status):
        """the init method"""
        Thread.__init__(self)
        self.__receiverName = receiverName
        self.__senderName = senderName
        self.__receiverRespIdentity = receiverRespIdentity
        self.__status = status
        self.__timestamp = timestamp
    
    def run(self):
        """This method forwards the call to the callbackPersonalMessage function."""
        callbackPersonalMessage(self.__receiverRespIdentity, self.__receiverName, self.__senderName, self.__timestamp, self.__status)


#####################
## execute threads ##
#####################

def executeHello(identity, isLogin):
    """This function executes a thread to search the responsible peer for authentication.

    Args:
        identity: the PeerInfo instance
        isLogin: A boolean determining whether the executed authentication is a registration or a login.
    """
    
    helloThread = HelloThread(identity, isLogin)
    helloThread.start()
    
    
def executeForwardRingReport(ringReportEntries, fingerValidationDict, backupValidationDict, alreadyReceivedUsers, type):
    """This function executes a thread that processes the ring report operation.
    
    Args:
        ringReportEntries: all collected RingReportEntry instances
        fingerValidationDict: a dictinary for validating the fingertables
        backupValidationDict: a dictinary for validating the backups
        alreadyReceivedUsers: a list of peers that already received the message
        type: the report type (defined in RingReportType)
    """
    
    forwardThread = ForwardRingReportThread(ringReportEntries, fingerValidationDict, backupValidationDict, alreadyReceivedUsers, type)
    forwardThread.start()


def executeSendMediaContent(content, timestamp, receiverName):
    """This function executes a thread that executes the reception of media contents.
    
    Args:
        content: the transmitted media content.
        timestamp: the timestamp
        receiverName: the name of the receiver (string)
    """
    
    mediaThread = SendMediaContentThread(content, timestamp, receiverName)
    mediaThread.start()


def executePollMediaContentSinceLastSynch(senderIdentity, senderName, receiverName, group, timestamp):
    """This function executes a thread that processes media content requests (poll).
    
    Args:
        senderIdentity: the sender identity (PeerInfo instance)
        senderName: the name of the sender (string)
        receiverName: the name of the receiver (string)
        group: the Group instance
        timestamp: the timestamp
    """
    
    getMedia = PollMediaContentSinceLastSynchThread(senderIdentity, senderName, receiverName, group, timestamp)
    getMedia.start()


def executeForwardPersonalMessage(receiverName, senderName, senderRespIdentity, message, timestamp):
    """This function executes a thread that processes the reception of a personal message.
    
    Args:
        receiverName: the name of the receiver (string)
        senderName: the name of the sender (string)
        senderRespIdentity: the responsible identity of the sender (PeerInfo instance)
        message: the personal message (string)
        timestamp: the timestamp
    """
    
    thread = ForwardPersonalMessageThread(receiverName, senderName, senderRespIdentity, message, timestamp)
    thread.start()
    
    
def executeCallbackPersonalMessage(receiverRespIdentity, receiverName, senderName, timestamp, status):
    """This function executes a thread that processes the callback to a sent personal message.
    
    Args:
        receiverRespIdentity: the responsible identity of the receiver (PeerInfo instance)
        receiverName: the name of the receiver (string)
        senderName: the name of the sender (string)
        timestamp: the timestamp
        status: the status 
    """
    
    thread = CallbackPersonalMessageThread(receiverRespIdentity, receiverName, senderName, timestamp, status)
    thread.start()
